/*
 * Simulation.cpp
 *
 *  Created on: Dec 15, 2014
 *      Author: ahueck
 */

#include "Simulation.h"
#include "Config.h"
#include "Body.h"
#include "Logger.h"

#include <gui/BodyVisualization.h>

#include <limits>
#include <vector>
#include <cmath>
#include <iostream>

namespace practical {
namespace nbody {

Simulation::Simulation(const Config& config) :
		config(config), num_steps(0u), num_bodies(0u), dt(0.0f), scale(
				1.0f), distance(0.05f) {
		body = new Body();

}

void Simulation::run() {
	for (unsigned int step = 0; step < num_steps; ++step) {
		nextTimestep();
	}
}

void Simulation::nextTimestep() {
		handleCollisions();
		int s = body->v.size();
		float accels[s][2];
		for(int i = 0; i < s; i++) {
			accels[i][0] = 0;
			accels[i][1] = 0;
			for (int k = 0; k < s; k++) {
				if (i != k) {
					float accel[2];
					if (body->v[i][Body::mass] > 0) {
						accel_routine(i, k, accel);
						accels[i][0] += accel[0];
						accels[i][1] += accel[1];
					}
				}
			}
		}

		for(int i = 0; i < s; i++){
			body->v[i][Body::vx] += (dt * accels[i][0]);
			body->v[i][Body::vy] += (dt * accels[i][1]);

			body->v[i][Body::x] = body->v[i][Body::x] + dt * body->v[i][Body::vx];
			body->v[i][Body::y] = body->v[i][Body::y] + dt * body->v[i][Body::vy];	
		}
}

const float G = 6.673e-11f;
const float eps = 0.000000000000000001;
void Simulation::accel_routine(int i, int k, float* out) {
	std::vector<float> vi = body->v[i];
	std::vector<float> vk = body->v[k];
	float delta_x = vk[Body::x] - vi[Body::x];
	float delta_y = vk[Body::y] - vi[Body::y];
	float r = sqrt(
		(delta_x * delta_x) + 
		(delta_y * delta_y) + 
		eps
	);
	float fac = 1.0 / (r * r * r); //OK
	float a = G * vk[Body::mass] * fac * (delta_x);
	out[0] = a;
	out[1] = G * vk[Body::mass] * fac * (delta_y);
}

void Simulation::handleCollisions() {
	int s = body->v.size();
	for(int i = 0; i < s; i++){
		for(int j = 0; j < s; j++){
			if (i != j){
				if (((body->v[i][Body::x] < body->v[j][Body::x] + 0.005) && (body->v[i][Body::x] > body->v[j][Body::x] - 0.005)) && ((body->v[i][Body::y] < body->v[j][Body::y] + 0.005) && (body->v[i][Body::y] > body->v[j][Body::y] - 0.005)) && body->v[j][Body::mass] != 0){
					body->v[i][Body::mass] = body->v[i][Body::mass] + body->v[j][Body::mass];
					body->v[i][Body::vx] = (body->v[i][Body::mass] * body->v[i][Body::vx] + body->v[j][Body::mass] * body->v[j][Body::vx]) / body->v[i][Body::mass];
					body->v[i][Body::vy] = (body->v[i][Body::mass] * body->v[i][Body::vy] + body->v[j][Body::mass] * body->v[j][Body::vy]) / body->v[i][Body::mass];  
					body->v[j][Body::mass] = 0;
				}
			}	
		}
	}
}

void Simulation::setMinMaxMass(ui::BodyVisualization& vis) const {
	float min = std::numeric_limits<float>::max();
	float max = std::numeric_limits<float>::min();
	// finds min and max mass of all bodies
	for (unsigned int i = 0; i < num_bodies; ++i) {
		const float mass = body->v[i][Body::mass];
		if (mass > max) {
			max = mass;
		}
		if (mass < min) {
			min = mass;
		}
	}
	vis.setMass(min, max);
}

void Simulation::visualize(ui::BodyVisualization& vis) const {
	const float p_scaler = 1.0f / scale;
	for (unsigned int i = 0;i < num_bodies; ++i) {
		if (body->v[i][Body::mass] > 0) {
			vis.addBody(body->v[i][Body::x] * p_scaler, body->v[i][Body::y] * p_scaler, body->v[i][Body::mass], body->v[i][Body::vx], body->v[i][Body::vy]); 
		}
	}
}

bool Simulation::initialize() {
	return config.read(body, num_steps, num_bodies, scale, dt, distance);
}

bool Simulation::writeResults() const {
	return config.write(body, num_steps, num_bodies, scale, dt, distance);
}

Simulation::~Simulation() {
	delete body;
}

} /* namespace nbody */
} /* namespace practical */
